package org.xtremeobjects.domain.states.annotated;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.xtremeobjects.anotations.state.Event;
import org.xtremeobjects.anotations.state.OnStateEntry;
import org.xtremeobjects.anotations.state.OnStateExit;
import org.xtremeobjects.anotations.state.StateField;
import org.xtremeobjects.anotations.state.StateMachine;
import org.xtremeobjects.anotations.state.SuperState;
import org.xtremeobjects.anotations.state.Transition;

public class StateMachineAnnotatedClass {

	private Class<?> clazz;

	public StateMachineAnnotatedClass( Class<?> clazz ) {
		this.clazz = clazz;
	}
	
	public boolean isValid()
	{
		return isValidAnnotatedClass();
	}

	public String getStateMachineName() {
		return clazz.getName();
	}

	public String[] getStates() {
		assertStateField();
	
		ArrayList<String> states = new ArrayList<String>();
		Field stateField = getStateField();
		if (stateField!=null && stateField.getType().getDeclaredFields()!=null ) {
			for (Field constant : stateField.getType().getDeclaredFields()) {
				if(constant.isEnumConstant() /*&& constant.getAnnotation(SuperState.class)==null/**/)
					states.add(constant.getName());
			}
		}
		return states.toArray(new String[states.size()]);
	}

	public Field getStateField() {
		for (Field field : clazz.getDeclaredFields()) {
			if (field.isAnnotationPresent(StateField.class) && field.getType().isEnum()) {
				return field;
			}
		}
		return null;
	}

	public String getStartState() {
		String initialState = getStateMachineAnnotation().initialState();
		assertValidState(initialState);
		return initialState; 
	}

	private void assertValidState(String state) {
		if(!isValidState(state))
			throw new RuntimeException("Invalid state in class " + clazz.getName() + " state: " + state );
	}

	private boolean isValidState(String state) {
		return (Arrays.asList(getStates()).contains(state) || state.equals("Any") );
	}

	private StateMachine getStateMachineAnnotation() {
		StateMachine stateMachine = clazz.getAnnotation(StateMachine.class);
		if(stateMachine==null)
			throw new RuntimeException("Class " + clazz.getName() + " is not annotated with @StateMachine annotation");
		return stateMachine;
	}

	public String getStateSuperState(String stateName) {
		assertValidState(stateName);
		Field field = getStateEnumConstantFor(stateName);
		SuperState superState = field.getAnnotation(SuperState.class);
		if(superState!=null) {
			assertValidState(superState.value());
			return superState.value();
		}
		return null;
	}

	private Field getStateEnumConstantFor(String stateName) {
		for (Field constant : getStateField().getType().getDeclaredFields()) {
			if(constant.isEnumConstant() && constant.getName().equals(stateName))
				return constant;
		}
		return null;
	}

	public List<Method> getEvents() {
		List<Method> methods = new ArrayList<Method>();
		for (Method method : clazz.getDeclaredMethods()) {
			if( method.isAnnotationPresent(Event.class) ) {
				assertValidEvent(method);
				methods.add(method);
			}
		}
		return methods;
	}
	
	private void assertValidEvent(Method method) {
		if(!isValidEvent(method))
			throw new RuntimeException("Event defition on method " + method.getName() + " is invalid");
	}

	private boolean isValidEvent(Method method) {
		Event eventAnnotation = getEventAnnotation(method);
		if(eventAnnotation!=null && areValidTransitions( eventAnnotation.value() ))
			return true;
		return false;
	}

	private boolean areValidTransitions(Transition[] transitions) {
		boolean valid = true;
		for (Transition transition : transitions) {
			if(!isValidTransition(transition))
				valid = false;
		}
		return valid;
	}

	private boolean isValidTransition(Transition transition) {
		if( isValidTransitionTo(transition) && isValidTransitionFrom(transition))
			return true;
		return false;
	}

	private boolean isValidTransitionFrom(Transition transition) {
		if(!areValidStates(transition.from()))
			return false;
		return true;
	}

	private boolean isValidTransitionTo(Transition transition) {
		if(!isValidState(transition.to()))
			return false;
		return true;
	}

	private boolean areValidStates(String[] states) {
		boolean valid = true;
		for (String state : states) {
			if(!isValidState(state))
				valid = false;
		}
		return valid;
	}

	private Event getEventAnnotation(Method method) {
		return method.getAnnotation(Event.class); 
	}

	public List<Method> getActions() {
		List<Method> methods = new ArrayList<Method>();
		
		for (Method method : clazz.getDeclaredMethods()) {
			if(method.isAnnotationPresent(OnStateEntry.class) || method.isAnnotationPresent(OnStateExit.class))
				methods.add(method);
		}
		
		return methods;
	}
	
	private boolean isValidAnnotatedClass()
	{
		if(
				hasStateField()
			)
			return true;
		return false;
	}
	
	private boolean hasStateField()
	{
		return getStateField()!=null;
	}
	
	private void assertStateField() {
		if(!hasStateField())
			throw new RuntimeException("Class " + clazz.getName() + " does not have defined a state field");
	}

}
